<?php

namespace Luracast\Restler\Format;

use Luracast\Restler\Data\Object;
use Luracast\Restler\RestException;

/**
 * Javascript Object Notation Format
 *
 * @category Framework
 * @package Restler
 * @subpackage format
 * @author R.Arul Kumaran <arul@luracast.com>
 * @copyright 2010 Luracast
 * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
 * @link http://luracast.com/products/restler/
 * @version 3.0.0rc5
 */
class JsonFormat extends Format {
	/**
	 *
	 * @var boolean|null shim for json_encode option JSON_PRETTY_PRINT set
	 *      it to null to use smart defaults
	 */
	public static $prettyPrint = null;
	
	/**
	 *
	 * @var boolean|null shim for json_encode option JSON_UNESCAPED_SLASHES
	 *      set it to null to use smart defaults
	 */
	public static $unEscapedSlashes = null;
	
	/**
	 *
	 * @var boolean|null shim for json_encode JSON_UNESCAPED_UNICODE set it
	 *      to null to use smart defaults
	 */
	public static $unEscapedUnicode = null;
	
	/**
	 *
	 * @var boolean|null shim for json_decode JSON_BIGINT_AS_STRING set it to
	 *      null to
	 *      use smart defaults
	 */
	public static $bigIntAsString = null;
	const MIME = 'application/json';
	const EXTENSION = 'json';
	public function encode($data, $humanReadable = false) {
		if (! is_null ( self::$prettyPrint )) {
			$humanReadable = self::$prettyPrint;
		}
		if (is_null ( self::$unEscapedSlashes )) {
			self::$unEscapedSlashes = $humanReadable;
		}
		if (is_null ( self::$unEscapedUnicode )) {
			self::$unEscapedUnicode = $this->charset == 'utf-8';
		}
		$options = 0;
		if ((PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION >= 4) || // PHP >= 5.4
PHP_MAJOR_VERSION > 5) // PHP >= 6.0
{
			if ($humanReadable)
				$options |= JSON_PRETTY_PRINT;
			if (self::$unEscapedSlashes)
				$options |= JSON_UNESCAPED_SLASHES;
			if (self::$bigIntAsString)
				$options |= JSON_BIGINT_AS_STRING;
			if (self::$unEscapedUnicode)
				$options |= JSON_UNESCAPED_UNICODE;
			return json_encode ( Object::toArray ( $data, true ), $options );
		}
		
		$result = json_encode ( Object::toArray ( $data, true ) );
		if ($humanReadable)
			$result = $this->formatJson ( $result );
		if (self::$unEscapedUnicode) {
			$result = preg_replace_callback ( '/\\\u(\w\w\w\w)/', function ($matches) {
				if (function_exists ( 'mb_convert_encoding' )) {
					return mb_convert_encoding ( pack ( 'H*', $matches [1] ), 'UTF-8', 'UTF-16BE' );
				} else {
					return iconv ( 'UTF-16BE', 'UTF-8', pack ( 'H*', $matches [1] ) );
				}
			}, $result );
		}
		if (self::$unEscapedSlashes)
			$result = str_replace ( '\/', '/', $result );
		return $result;
	}
	public function decode($data) {
		$options = 0;
		if (self::$bigIntAsString) {
			if ((PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION >= 4) || // PHP >= 5.4
PHP_MAJOR_VERSION > 5) // PHP >= 6.0
{
				$options |= JSON_BIGINT_AS_STRING;
			} else {
				$data = preg_replace ( '/:\s*(\-?\d+(\.\d+)?([e|E][\-|\+]\d+)?)/', ': "$1"', $data );
			}
		}
		$decoded = json_decode ( $data, $options );
		if (function_exists ( 'json_last_error' )) {
			switch (json_last_error ()) {
				case JSON_ERROR_NONE :
					return Object::toArray ( $decoded );
					break;
				case JSON_ERROR_DEPTH :
					$message = 'maximum stack depth exceeded';
					break;
				case JSON_ERROR_STATE_MISMATCH :
					$message = 'underflow or the modes mismatch';
					break;
				case JSON_ERROR_CTRL_CHAR :
					$message = 'unexpected control character found';
					break;
				case JSON_ERROR_SYNTAX :
					$message = 'malformed JSON';
					break;
				case JSON_ERROR_UTF8 :
					$message = 'malformed UTF-8 characters, possibly ' . 'incorrectly encoded';
					break;
				default :
					$message = 'unknown error';
					break;
			}
			throw new RestException ( 400, 'Error parsing JSON, ' . $message );
		} elseif (strlen ( $data ) && $decoded === null || $decoded === $data) {
			throw new RestException ( 400, 'Error parsing JSON' );
		}
		
		return Object::toArray ( $decoded );
	}
	
	/**
	 * Pretty print JSON string
	 *
	 * @param string $json        	
	 *
	 * @return string formatted json
	 */
	private function formatJson($json) {
		$tab = '  ';
		$newJson = '';
		$indentLevel = 0;
		$inString = false;
		$len = strlen ( $json );
		for($c = 0; $c < $len; $c ++) {
			$char = $json [$c];
			switch ($char) {
				case '{' :
				case '[' :
					if (! $inString) {
						$newJson .= $char . "\n" . str_repeat ( $tab, $indentLevel + 1 );
						$indentLevel ++;
					} else {
						$newJson .= $char;
					}
					break;
				case '}' :
				case ']' :
					if (! $inString) {
						$indentLevel --;
						$newJson .= "\n" . str_repeat ( $tab, $indentLevel ) . $char;
					} else {
						$newJson .= $char;
					}
					break;
				case ',' :
					if (! $inString) {
						$newJson .= ",\n" . str_repeat ( $tab, $indentLevel );
					} else {
						$newJson .= $char;
					}
					break;
				case ':' :
					if (! $inString) {
						$newJson .= ': ';
					} else {
						$newJson .= $char;
					}
					break;
				case '"' :
					if ($c == 0) {
						$inString = true;
					} elseif ($c > 0 && $json [$c - 1] != '\\') {
						$inString = ! $inString;
					}
				default :
					$newJson .= $char;
					break;
			}
		}
		
		return $newJson;
	}
}

